/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup edgizmolib
 *
 * \name Button Gizmo
 *
 * 2D Gizmo, also works in 3D views.
 *
 * \brief Single click button action for use in gizmo groups.
 *
 * \note Currently only basic icon & vector-shape buttons are supported.
 */

#include "MEM_guardedalloc.h"

#include "BLI_math.h"

#include "BKE_context.h"

#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_select.h"
#include "GPU_batch.h"
#include "GPU_batch_utils.h"
#include "GPU_state.h"

#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"

#include "WM_api.h"
#include "WM_types.h"

#include "ED_screen.h"
#include "ED_view3d.h"
#include "ED_gizmo_library.h"

#include "UI_interface.h"
#include "UI_interface_icons.h"
#include "UI_resources.h"

/* own includes */
#include "../gizmo_geometry.h"
#include "../gizmo_library_intern.h"

typedef struct ButtonGizmo2D {
  wmGizmo gizmo;
  bool is_init;
  /* Use an icon or shape */
  int icon;
  GPUBatch *shape_batch[2];
} ButtonGizmo2D;

#define CIRCLE_RESOLUTION 32

/* -------------------------------------------------------------------- */

static void button2d_geom_draw_backdrop(const wmGizmo *gz, const float color[4], const bool select)
{
  GPU_line_width(gz->line_width);

  GPUVertFormat *format = immVertexFormat();
  uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);

  immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);

  immUniformColor4fv(color);

  /* TODO, other draw styles */
  imm_draw_circle_fill_2d(pos, 0, 0, 1.0f, CIRCLE_RESOLUTION);

  immUnbindProgram();

  UNUSED_VARS(select);
}

static void button2d_draw_intern(const bContext *C,
                                 wmGizmo *gz,
                                 const bool select,
                                 const bool highlight)
{
  ButtonGizmo2D *button = (ButtonGizmo2D *)gz;

  const int draw_options = RNA_enum_get(gz->ptr, "draw_options");
  if (button->is_init == false) {
    button->is_init = true;
    PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon");
    if (RNA_property_is_set(gz->ptr, prop)) {
      button->icon = RNA_property_enum_get(gz->ptr, prop);
    }
    else {
      prop = RNA_struct_find_property(gz->ptr, "shape");
      const uint polys_len = RNA_property_string_length(gz->ptr, prop);
      /* We shouldn't need the +1, but a NULL char is set. */
      char *polys = MEM_mallocN(polys_len + 1, __func__);
      RNA_property_string_get(gz->ptr, prop, polys);
      button->shape_batch[0] = GPU_batch_tris_from_poly_2d_encoded(
          (uchar *)polys, polys_len, NULL);
      button->shape_batch[1] = GPU_batch_wire_from_poly_2d_encoded(
          (uchar *)polys, polys_len, NULL);
      MEM_freeN(polys);
    }
  }

  float color[4];
  float matrix_final[4][4];

  gizmo_color_get(gz, highlight, color);
  WM_gizmo_calc_matrix_final(gz, matrix_final);

  bool is_3d = (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) != 0;

  if ((select == false) && (draw_options & ED_GIZMO_BUTTON_SHOW_HELPLINE)) {
    float matrix_final_no_offset[4][4];
    WM_gizmo_calc_matrix_final_no_offset(gz, matrix_final_no_offset);
    uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
    immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
    immUniformColor4fv(color);
    GPU_line_width(gz->line_width);
    immUniformColor4fv(color);
    immBegin(GPU_PRIM_LINE_STRIP, 2);
    immVertex3fv(pos, matrix_final[3]);
    immVertex3fv(pos, matrix_final_no_offset[3]);
    immEnd();
    immUnbindProgram();
  }

  bool need_to_pop = true;
  GPU_matrix_push();
  GPU_matrix_mul(matrix_final);

  if (is_3d) {
    RegionView3D *rv3d = CTX_wm_region_view3d(C);
    float matrix_align[4][4];
    float matrix_final_unit[4][4];
    normalize_m4_m4(matrix_final_unit, matrix_final);
    mul_m4_m4m4(matrix_align, rv3d->viewmat, matrix_final_unit);
    zero_v3(matrix_align[3]);
    transpose_m4(matrix_align);
    GPU_matrix_mul(matrix_align);
  }

  if (select) {
    BLI_assert(is_3d);
    button2d_geom_draw_backdrop(gz, color, select);
  }
  else {

    GPU_blend(true);
    if (button->shape_batch[0] != NULL) {
      GPU_line_smooth(true);
      GPU_polygon_smooth(false);
      GPU_line_width(1.0f);
      for (uint i = 0; i < ARRAY_SIZE(button->shape_batch) && button->shape_batch[i]; i++) {
        /* Invert line color for wire. */
        GPU_batch_program_set_builtin(button->shape_batch[i], GPU_SHADER_2D_UNIFORM_COLOR);
        GPU_batch_uniform_4f(button->shape_batch[i], "color", UNPACK4(color));
        GPU_batch_draw(button->shape_batch[i]);

        if (draw_options & ED_GIZMO_BUTTON_SHOW_OUTLINE) {
          color[0] = 1.0f - color[0];
          color[1] = 1.0f - color[1];
          color[2] = 1.0f - color[2];
        }
      }
      GPU_line_smooth(false);
      GPU_polygon_smooth(true);
    }
    else if (button->icon != ICON_NONE) {
      if (draw_options & ED_GIZMO_BUTTON_SHOW_BACKDROP) {
        button2d_geom_draw_backdrop(gz, color, select);
      }

      float pos[2];
      if (is_3d) {
        const float fac = 2.0f;
        GPU_matrix_translate_2f(-(fac / 2), -(fac / 2));
        GPU_matrix_scale_2f(fac / (ICON_DEFAULT_WIDTH * UI_DPI_FAC),
                            fac / (ICON_DEFAULT_HEIGHT * UI_DPI_FAC));
        pos[0] = 1.0f;
        pos[1] = 1.0f;
      }
      else {
        pos[0] = gz->matrix_basis[3][0] - (ICON_DEFAULT_WIDTH / 2.0) * UI_DPI_FAC;
        pos[1] = gz->matrix_basis[3][1] - (ICON_DEFAULT_HEIGHT / 2.0) * UI_DPI_FAC;
        GPU_matrix_pop();
        need_to_pop = false;
      }

      float alpha = (highlight) ? 1.0f : 0.8f;
      GPU_polygon_smooth(false);
      UI_icon_draw_alpha(pos[0], pos[1], button->icon, alpha);
      GPU_polygon_smooth(true);
    }
    GPU_blend(false);
  }

  if (need_to_pop) {
    GPU_matrix_pop();
  }
}

static void gizmo_button2d_draw_select(const bContext *C, wmGizmo *gz, int select_id)
{
  GPU_select_load_id(select_id);
  button2d_draw_intern(C, gz, true, false);
}

static void gizmo_button2d_draw(const bContext *C, wmGizmo *gz)
{
  const bool is_highlight = (gz->state & WM_GIZMO_STATE_HIGHLIGHT) != 0;

  GPU_blend(true);
  button2d_draw_intern(C, gz, false, is_highlight);
  GPU_blend(false);
}

static int gizmo_button2d_test_select(bContext *C, wmGizmo *gz, const int mval[2])
{
  float point_local[2];

  if (0) {
    /* correct, but unnecessarily slow. */
    if (gizmo_window_project_2d(C, gz, (const float[2]){UNPACK2(mval)}, 2, true, point_local) ==
        false) {
      return -1;
    }
  }
  else {
    copy_v2_v2(point_local, (float[2]){UNPACK2(mval)});
    sub_v2_v2(point_local, gz->matrix_basis[3]);
    mul_v2_fl(point_local, 1.0f / (gz->scale_basis * UI_DPI_FAC));
  }
  /* The 'gz->scale_final' is already applied when projecting. */
  if (len_squared_v2(point_local) < 1.0f) {
    return 0;
  }

  return -1;
}

static int gizmo_button2d_cursor_get(wmGizmo *gz)
{
  if (RNA_boolean_get(gz->ptr, "show_drag")) {
    return BC_NSEW_SCROLLCURSOR;
  }
  return CURSOR_STD;
}

static void gizmo_button2d_free(wmGizmo *gz)
{
  ButtonGizmo2D *shape = (ButtonGizmo2D *)gz;

  for (uint i = 0; i < ARRAY_SIZE(shape->shape_batch); i++) {
    GPU_BATCH_DISCARD_SAFE(shape->shape_batch[i]);
  }
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Button Gizmo API
 *
 * \{ */

static void GIZMO_GT_button_2d(wmGizmoType *gzt)
{
  /* identifiers */
  gzt->idname = "GIZMO_GT_button_2d";

  /* api callbacks */
  gzt->draw = gizmo_button2d_draw;
  gzt->draw_select = gizmo_button2d_draw_select;
  gzt->test_select = gizmo_button2d_test_select;
  gzt->cursor_get = gizmo_button2d_cursor_get;
  gzt->free = gizmo_button2d_free;

  gzt->struct_size = sizeof(ButtonGizmo2D);

  /* rna */
  static EnumPropertyItem rna_enum_draw_options[] = {
      {ED_GIZMO_BUTTON_SHOW_OUTLINE, "OUTLINE", 0, "Outline", ""},
      {ED_GIZMO_BUTTON_SHOW_BACKDROP, "BACKDROP", 0, "Backdrop", ""},
      {ED_GIZMO_BUTTON_SHOW_HELPLINE, "HELPLINE", 0, "Help Line", ""},
      {0, NULL, 0, NULL, NULL},
  };
  PropertyRNA *prop;

  RNA_def_enum_flag(gzt->srna, "draw_options", rna_enum_draw_options, 0, "Draw Options", "");

  prop = RNA_def_property(gzt->srna, "icon", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_items(prop, rna_enum_icon_items);

  /* Passed to 'GPU_batch_tris_from_poly_2d_encoded' */
  RNA_def_property(gzt->srna, "shape", PROP_STRING, PROP_BYTESTRING);

  /* Currently only used for cursor display. */
  RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", "");
}

void ED_gizmotypes_button_2d(void)
{
  WM_gizmotype_append(GIZMO_GT_button_2d);
}

/** \} */  // Button Gizmo API
